﻿using System;
using System.Collections.Generic;
using System.Linq;
using TradeHelp.MaterializedObjectViews.Views.Caches;
using TradeHelp.Utilities;

namespace TradeHelp.MaterializedObjectViews.Views
{
    /// <summary>
    /// 
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class SelectQueryView<T> : QueryView<T>
    {
        private readonly IViewCache<T> _cache;
        private readonly IViewStream<T> _provider;
        private readonly MsgSequencer<T> _sequencer = new MsgSequencer<T>();
        private bool _disposed;
        private int _cacheVersion;
        private StreamMessageDelegate<T> _callbacks;

        /// <summary>
        /// 
        /// </summary>
        /// <param name="provider"></param>
        public SelectQueryView(IViewStream<T> provider)
            : this(provider, new ViewCache<T>())
        {
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="provider"></param>
        /// <param name="cache"></param>
        public SelectQueryView(IViewStream<T> provider, IViewCache<T> cache)
        {
            Invariant.CheckIsNull(provider, "provider");
            Invariant.CheckIsNull(cache, "cache");
            _provider = provider;
            _cache = cache;
        }

        /// <summary>
        /// Incrementing number represents the change version of the data
        /// contained within the view.
        /// </summary>
        /// <returns></returns>
        public override int GetVersion()
        {
            lock(_cache)
            {
                return _cacheVersion;
            }
        }

        /// <summary>
        /// Requests an read only snapshot of the data contained within the view.
        /// </summary>
        /// <returns>collection of unchanging data</returns>
        public override ViewData<T> GetData()
        {
            List<T> data;
            int version;
            lock (_cache)
            {
                version = _cacheVersion;
                data = _cache.ToList();
            }
            return new ViewData<T>(data, version);
        }

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        /// <filterpriority>2</filterpriority>
        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                _disposed = true;
                _provider.Unsubscribe(ViewMsgBurstHandler);
                var disposable = _provider as IDisposable;
                if (disposable != null)
                {
                    disposable.Dispose();
                }
                lock (_cache)
                {
                    _cache.Clear();
                    _callbacks = null;
                }
            }
        }

        /// <summary>
        /// 
        /// </summary>
        protected override void StreamDataCore()
        {
            _provider.StreamData(ViewMsgBurstHandler);
        }

        /// <summary>
        /// Request to unsubscribe client code from recieving further incremental updates
        /// </summary>
        /// <param name="callback"></param>
        protected override void UnsubscribeCore(StreamMessageDelegate<T> callback)
        {
            lock (_cache)
            {
                _callbacks -= callback;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="callback"></param>
        protected override ViewData<T> SubscribeCore(StreamMessageDelegate<T> callback)
        {
            lock (_cache)
            {
                _callbacks += callback;
                return new ViewData<T>(_cache.ToList(), _cacheVersion);
            }
        }
        
        private void ViewMsgBurstHandler(ViewMessageBurst<T> burst)
        {
            Invariant.CheckIsNull(burst, "burst");
            if (_disposed) return;

            int cacheVersion;
            List<ViewMessage<T>> data = null;
            StreamMessageDelegate<T> callbacks = null;

            lock (_cache)
            {
                foreach (var msg in _sequencer.Resequence(burst))
                {
                    ViewMessage<T> newMessage;
                    if (!_cache.TryProcess(msg, out newMessage)) continue;
                    data = data ?? new List<ViewMessage<T>>();
                    data.Add(newMessage);
                }

                if (data == null) return;
                cacheVersion = ++_cacheVersion;
                callbacks += _callbacks;
            }

            if (callbacks != null)
            {
                callbacks(new ViewMessageBurst<T>(data, cacheVersion));
            }
        }
    }
}